package com.cyou.framework.pagination.proxy;

import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.session.SqlSession;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * PaginationMapperProxy
 *
 * @since JDK 1.5
 * @version 1.0 2012-12-15
 * @author zhangxingkai
 */
public class PaginationMapperProxy implements InvocationHandler, Serializable {

	private static final long serialVersionUID = 5018029243120226326L;
    private SqlSession sqlSession;

    private <T> PaginationMapperProxy(final SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
    	if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }
    	
        final Class<?> declaringInterface = findDeclaringInterface(proxy, method);

        if (PaginationSupport.class.isAssignableFrom(method.getReturnType())) {
            // 分页处理
        	final PaginationMapperMethod paginationMapperMethod = new PaginationMapperMethod(declaringInterface, method, sqlSession);
            final Object paginationResult = paginationMapperMethod.execute(args);
            return paginationResult;
        }
        
        // 原处理方式
        final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
        final Object result = mapperMethod.execute(args);
        
        if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
            throw new BindingException(
                    "Mapper method '"
                            + method.getName()
                            + "' ("
                            + method.getDeclaringClass()
                            + ") attempted to return null from a method with a primitive return type ("
                            + method.getReturnType() + ").");
        }
        return result;
    }

    private Class<?> findDeclaringInterface(Object proxy, Method method) {
        Class<?> declaringInterface = null;
        for (Class<?> mapperFaces : proxy.getClass().getInterfaces()) {
        	try {
	            Method m = mapperFaces.getMethod(method.getName(), method.getParameterTypes());
	            if(declaringInterface != null) {
	            	throw new BindingException("Ambiguous method mapping.  Two mapper interfaces contain the identical method signature for " + method);
	            }
	            if (m != null) {
	                declaringInterface = mapperFaces;
	            }
            } catch(Exception e) { }
        }
        if (declaringInterface == null) {
            throw new BindingException(
                    "Could not find interface with the given method " + method);
        }
        return declaringInterface;
    }

    @SuppressWarnings("unchecked")
    public static <T> T newMapperProxy(Class<T> mapperInterface,
                                       SqlSession sqlSession) {
        ClassLoader classLoader = mapperInterface.getClassLoader();
        Class<?>[] interfaces = new Class[]{mapperInterface};
        PaginationMapperProxy proxy = new PaginationMapperProxy(sqlSession);
        return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
    }
}
